package com.softwarecraftsmen.cachecontrol.caches;

import com.softwarecraftsmen.cachecontrol.CacheItem;
import com.softwarecraftsmen.cachecontrol.Item;
import com.softwarecraftsmen.cachecontrol.keys.Key;
import org.jetbrains.annotations.NotNull;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;

// TODO: Improve threading by internal partitioning (eg 16 Lists, 16 HashMaps)
public class SizeLimitedCache<V> extends AbstractComptrolledCache<V>
{
	private final ConcurrentHashMap<Key<V>, V> internalCache;
	private final Queue<Key<V>> entries;
	private final Object lock;
	private final int maximumSize;

	public SizeLimitedCache(@NotNull final Key<V> identifier, final int maximumSize)
	{
		super(identifier);
		this.maximumSize = maximumSize;
		if (maximumSize < 1)
		{
			throw new IllegalArgumentException("Negative or zero sizes do not make sense for SizeLimitedCache");
		}
		lock = new Object();
		internalCache = new ConcurrentHashMap<Key<V>, V>();
		entries = new LinkedList<Key<V>>();
	}

	@NotNull
	public Item<V> obtain(@NotNull final Key<V> key)
	{
		final V value = internalCache.get(key);
		return new CacheItem<V>(this, key, value);
	}

	//TODO: Does not work! We need to check if it already exists...
	//TODO: There is no shuffling to back if a key is re-added recently!
	/*
		There are four cache eviction algorithms:-
		Least Recently Used (good default)
		Lest Frequently Used (do not do over all elements, select a random sample and evict the least frequently used)
		First in, First Out (what we are currently trying to do)
		Domain chosen (best, if detailed domain knowledge exists)



	 */
	@NotNull
	public Item<V> store(final @NotNull Key<V> key, final @NotNull V value)
	{
		synchronized (lock)
		{
			//if (internalCache.containsKey(key) &&
			// Consider storing the values in a list instead and the index positions in the Map!


			if (entries.size() >= maximumSize)
			{
				final Key<V> oldItemKey = entries.remove();
				internalCache.remove(oldItemKey);
			}
			entries.offer(key);
			internalCache.put(key, value);
		}
		return new CacheItem<V>(this, key, value);
	}

	public void invalidate()
	{
		synchronized (lock)
		{
			internalCache.clear();
			entries.clear();
		}
	}
}
